package rensoft.android.view.grid;

import static rensoft.android.lang.AndroidLang.event;
import static rensoft.java.lang.Lang.is;
import static rensoft.java.lang.Lang.list;
import static rensoft.java.lang.Lang.map;
import static rensoft.java.lang.Lang.no;

import rensoft.android.view.requestdata.IRequestData;
import rensoft.java.collections.List;
import rensoft.java.collections.Map;
import rensoft.java.event.Event;
import rensoft.java.event.Event.EventRegistration;
import rensoft.java.event.Listener;
import rensoft.java.lang.Base;
import rensoft.java.lang.Callback;

public abstract class GridRequestData<T extends IGridRequest> extends Base implements
    IRequestData<T> {
  private String fromtime;
  private final Map<Integer, T> requests = map();
  private int allCount;
  private final Event<T> eventLoaded = event();
  private final Event<Void> eventFailed = event();
  private boolean waiting;
  private T request;
  private Callback<T> createRequestListener;
  private final List<RowData> prependData = list();

  public Event<Void> eventFailed() {
    return eventFailed;
  }

  public Event<T> eventLoaded() {
    return eventLoaded;
  }

  public int getAllCount() {
    return prependData.size() + allCount;
  }

  @Override
  public T getDataRequest() {
    return request;
  }

  public T getRequestFor(final int position) {
    int requestIndex = position / 30;
    T request;
    if (requests.hasKey(requestIndex))
      request = requests.value(requestIndex);
    else {
      request = createRequest();
      if (is(createRequestListener)) createRequestListener.onCall(request);
      request.setIndex(requestIndex);
      request.setFromTime(fromtime);
      request.send();
      requests.put(requestIndex, request);
    }
    return request;
  }

  public RowData getRowDataFor(int position) {
    if (position < prependData.size()) return prependData.get(0);
    position -= prependData.size();
    int rowIndexInRequest = position - position / 30 * 30;
    T request = getRequestFor(position);
    return new RequestRowData<T>(request, rowIndexInRequest);
  }

  @Override
  public boolean isReady() {
    return !waiting && is(request);
  }

  @Override
  public void load() {
    load(0);
  }

  public void load(int index) {
    if (isFirstLoad()) {
      reload(index);
      return;
    }
    request = getRequestFor(index);
    if (request.isDone()) {
      waiting = false;
      fire(eventLoaded, request);
    } else {
      waiting = true;
      final T request = this.request;
      listenOnce(request.eventLoaded(), new Listener<Void>() {
        public void onEvent(EventRegistration registration, Void arg) {
          onRequestLoadedOnLoad(request);
        }
      });
      listenOnce(request.eventFailed(), new Listener<Void>() {
        public void onEvent(EventRegistration registration, Void arg) {
          onRequestFailedOnLoad(request);
        }
      });
    }
  }

  public void prependData(RowData data) {
    prependData.add(0, data);
  }

  public void reload() {
    reload(0);
  }

  public void reload(int position) {
    requests.clear();
    prependData.clear();
    int requestIndex = position / 30;

    request = createRequest();
    if (is(createRequestListener)) createRequestListener.onCall(request);
    final T request = this.request;

    listenOnce(request.eventLoaded(), new Listener<Void>() {
      public void onEvent(EventRegistration registration, Void arg) {
        onRequestLoadedOnReload(request);
      }
    });
    request.setIndex(requestIndex);
    request.send();
    waiting = true;
    requests.put(requestIndex, request);
  }

  public void setCreateRequestListener(Callback<T> callback) {
    createRequestListener = callback;
  }

  protected abstract T createRequest();

  private boolean isFirstLoad() {
    return !waiting && no(request) && getAllCount() == 0;
  }

  private void onRequestFailedOnLoad(final T request) {
    if (request != GridRequestData.this.request) return;
    waiting = false;
    fire(eventFailed);
  }

  private void onRequestLoadedOnLoad(final T request) {
    if (request != GridRequestData.this.request) return;
    waiting = false;
    fire(eventLoaded, request);
  }

  private void onRequestLoadedOnReload(final T request) {
    if (request != GridRequestData.this.request) return;
    waiting = false;
    fromtime = request.getFromTime();
    allCount = request.getAllCount();
    fire(eventLoaded, request);
  }

}
